package top.ply.authservice.service.impl;

import com.sun.org.apache.xpath.internal.operations.Bool;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.annotation.Reference;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import top.ply.authservice.dao.CommonUserMapper;
import top.ply.authservice.dao.StatusMapper;
import top.ply.authservice.mq.SendVerifyCodeQueue;
import top.ply.authservice.mq.UserLoginEventQueue;
import top.ply.authservice.pojo.BaseUser;
import top.ply.authservice.pojo.CommonUser;
import top.ply.authservice.pojo.Status;
import top.ply.authservice.service.CommonAuthService;
import top.ply.authservice.service.CommonLoginWorker;
import top.ply.common_unit.entity.RespEntity;
import top.ply.common_unit.global_resp.RespUtil;
import top.ply.common_unit.token.JWTUtil;
import top.ply.message.service.EmailMsgService;
import top.ply.message.service.TelephoneMsgService;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author: Amosen
 *
 * 普通用户登录服务写完了，总体思路如下：
 * 1. 根据不同情况分为几种情况进行登录：
 *  1） 根据用户名和密码进行登录
 *  2） 根据手机号和密码进行登录
 *  3） 根据邮箱和密码进行登录
 *  4） 根据手机号下发验证码验证登录
 *  5） 根据邮箱下发验证码验证登录
 * 2. 业务逻辑大体可以概括为：
 *  1） 第一步先校验参数，参数合格再继续进行下一步
 *  2） 由密码做凭证进行登陆的，将密码做MD5加密后与数据库保存密码进行对比，这意味着之后绑定账号密码的时候需要进行一次MD5加密
 *  3) 由验证码做凭证进行登陆的，首先生成验证码，然后保存到Redis服务缓存，
 *      然后校验数据库是否存在该手机号或邮箱信息，如果有，则不操作，如果没有，则尝试将数据保存到数据库，并在验证验证码时进行第二次保存尝试
 *      之后调用微服务消息组件下发验证码。
 *      在用户登录成功后，向事件线程池提交事件通知，方便上游服务获取事件通知
 *
 *      TODO
 * 3. 之后要做的事：
 *  1） 继续完成认证服务，完成手机号与邮箱的绑定
 *  2） 暴露接口给其他服务查询用户信息，
 *  这里要考虑是否使用两套接口，一套面向微信小程序，一套面向普通登录用户，使用微信的OAuth2或许可以合并接口，可以之后去看看微信的官方文档
 *  3） 还要做公众号的服务器接入
 *  4） 接口测试
 */

@Service
@RefreshScope
public class CommonLoginWorkerImpl extends BaseAuthService implements CommonLoginWorker, CommonAuthService {

    @Autowired
    CommonUserMapper commonUserMapper;

    @Autowired
    StatusMapper statusMapper;

    @Resource(name = "telephoneCodeRedisTemplate", type = StringRedisTemplate.class)
    StringRedisTemplate telephoneCodeRedisTemplate;

    @Resource(name = "emailCodeRedisTemplate", type = StringRedisTemplate.class)
    StringRedisTemplate emailCodeRedisTemplate;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Value("${spring.rabbitmq.verify-code-exchange.name}")
    private String verifyCodeExchangeName;

    @Value("${spring.rabbitmq.email-verify-code-queue.routingKey}")
    private String emailVerifyCodeQueueRoutingKey;

    @Value("${spring.rabbitmq.phone-verify-code-queue.routingKey}")
    private String phoneVerifyCodeQueueRoutingKey;

    @Value("${spring.rabbitmq.user-login-event-publish-exchange.name}")
    private String userLoginEventExchange;

    @Value("${spring.rabbitmq.user-login-event-publish-queue.routingKey}")
    private String userLoginEventQueueRoutingKey;


    static final Integer verifyCodeExpireTime = 5;

    static final TimeUnit verifyCodeExpireTimeUnit = TimeUnit.MINUTES;

    static final ExecutorService COMMON_USER_LOGIN_EVENT_POOL = Executors.newCachedThreadPool();

    public static final Integer NORMAL_CODE = 0;

    public static final Integer ILLEGAL_PARAMETER_CODE = 1;

    public static final Integer WRONG_USER_STATE_CODE = 2;

    public static final Integer WRONG_ACCOUNT_OR_PASSWORD = 3;

    private Integer errReason = NORMAL_CODE;

    @Override
    public String getUserIDFromToken(String token) {
        return JWTUtil.getPayload(token, authServiceSecretKey).get(PAYLOAD_USER_ID_KEY);
    }

    @Override
    public boolean isNormal(String token) {
        String userID = getUserIDFromToken(token);

        Map<String, Integer> data = new HashMap<>();

        CountDownLatch latch = new CountDownLatch(2);

        new Thread(() -> {
            Integer normalCode = statusMapper.getNormalStatusCode();
            data.put("normal_code", normalCode);
            latch.countDown();
        }).start();

        new Thread(() -> {
            CommonUser user = commonUserMapper.getUserByID(userID);
            data.put("user_status_code", user.getStatus().getStatusCode());
            latch.countDown();
        }).start();

        try {
            latch.await(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            return false;
        }

        return data.get("normal_code").equals(data.get("user_status_code"));
    }

    @Override
    public String commonLoginWithEmail(String email, String password) {
        Matcher matcher = EMAIL_PATTERN.matcher(email);
        if (matcher.matches()) {
            CommonUser user = commonUserMapper.getUserByEmail(email);
            if (user == null) {
                errReason = WRONG_ACCOUNT_OR_PASSWORD;
                return null;
            }

            if (!checkUserStatus(user)) {
                errReason = WRONG_USER_STATE_CODE;
                return null;
            }

            String encryptPass = DigestUtils.md5DigestAsHex(password.getBytes());
            if (encryptPass.equals(user.getPassword())) {
                Map<String, String> payload = new HashMap<>();
                payload.put(PAYLOAD_USER_ID_KEY, user.getUserID());

                // 发布用户登录事件
                new Thread(() -> {
                    publishUserLoginEvent(user.getUserID());
                }).start();

                return generateToken(payload);
            }
        }
        errReason = ILLEGAL_PARAMETER_CODE;
        return null;
    }

    @Override
    public String commonLoginWithTelephone(String telephone, String password) {
        Matcher matcher = PHONE_PATTERN.matcher(telephone);
        if (matcher.matches()) {
            CommonUser user = commonUserMapper.getUserByTelephone(telephone);
            if (user == null) {
                errReason = WRONG_ACCOUNT_OR_PASSWORD;
                return null;
            }

            if (!checkUserStatus(user)) {
                errReason = WRONG_USER_STATE_CODE;
                return null;
            }

            String encryptPass = DigestUtils.md5DigestAsHex(password.getBytes());
            if (encryptPass.equals(user.getPassword())) {
                Map<String, String> payload = new HashMap<>();
                payload.put(PAYLOAD_USER_ID_KEY, user.getUserID());

                new Thread(() -> {
                    publishUserLoginEvent(user.getUserID());
                }).start();

                return generateToken(payload);
            }
        }
        errReason = ILLEGAL_PARAMETER_CODE;
        return null;
    }


    @Override
    public String commonLoginWithAccount(String username, String password) {
        CommonUser user = commonUserMapper.getUserByUsername(username);
        if (user == null) {
            errReason = WRONG_ACCOUNT_OR_PASSWORD;
            return null;
        } else {

            if (!checkUserStatus(user)) {
                errReason = WRONG_USER_STATE_CODE;
                return null;
            }

            String encryptPass = DigestUtils.md5DigestAsHex(password.getBytes());
            if (encryptPass.equals(user.getPassword())) {
                Map<String, String> payload = new HashMap<>();
                payload.put(PAYLOAD_USER_ID_KEY, user.getUserID());

                // 发布用户登录事件
                new Thread(() -> {
                    publishUserLoginEvent(user.getUserID());
                }).start();

                return generateToken(payload);
            }
        }
        errReason = WRONG_ACCOUNT_OR_PASSWORD;
        return null;
    }

    @Override
    @Transactional
    public RespEntity sendCodeWithEmail(String email) {
        if (EMAIL_PATTERN.matcher(email).matches()) {

            CommonUser userByEmail = commonUserMapper.getUserByEmail(email);
            if (userByEmail != null) {
                if (!checkUserStatus(userByEmail)) {
                    return null;
                }
            }

            String verifyCode = generateVerifyCode();

            Long add = emailCodeRedisTemplate.opsForSet().add(email, verifyCode);
            emailCodeRedisTemplate.expire(email, verifyCodeExpireTime, verifyCodeExpireTimeUnit);


//            new Thread(() -> {
//                // Dubbo调用微服务组件下发邮箱验证码
//                try {
//                    boolean isSent = emailMsgService.sendVerifyCode(email, verifyCode);
//                    result.put("send_result", isSent);
//                } catch (Exception e) {
//                    result.put("send_result", false);
//                    e.printStackTrace();
//                } finally {
//                    latch.countDown();
//                }
//            }).start();

            // 解耦为消息队列的方式下发验证码
            new Thread(() -> {
                Map<String, String> mailAndCode = new HashMap<>();
                mailAndCode.put(EmailMsgService.sendToKey, email);
                mailAndCode.put(EmailMsgService.verifyCodeKey, verifyCode);
                Message<Map<String, String>> message = MessageBuilder.withPayload(mailAndCode).build();
                rabbitTemplate.convertAndSend(verifyCodeExchangeName, emailVerifyCodeQueueRoutingKey, message);
            }).start();

            if (add > 0) {
                new Thread(() -> {
                    CommonUser savedUser = commonUserMapper.getUserByEmail(email);
                    if (savedUser == null) {
                        CommonUser commonUser = new CommonUser();
                        commonUser.setEmail(email);
                        commonUser.setUserID(generateUserID(email));

                        Status status = new Status();
                        status.setStatusCode(statusMapper.getNormalStatusCode());
                        commonUser.setStatus(status);

                        commonUserMapper.addCommonUser(commonUser);
                    }
                }).start();
                return RespUtil.baseSuccess();
            }
        }
        return RespUtil.illegalParameter();
    }

    @Override
    @Transactional
    public RespEntity sendCodeWithTelephone(String telephone) {
        if (PHONE_PATTERN.matcher(telephone).matches()) {

            CommonUser userByTelephone = commonUserMapper.getUserByTelephone(telephone);
            if (userByTelephone != null) {
                if (!checkUserStatus(userByTelephone)) {
                    return null;
                }
            }

            String verifyCode = generateVerifyCode();

            Long add = emailCodeRedisTemplate.opsForSet().add(telephone, verifyCode);
            emailCodeRedisTemplate.expire(telephone, verifyCodeExpireTime, verifyCodeExpireTimeUnit);


//            new Thread(() -> {
//                try {
//                    boolean isSend = telephoneMsgService.sendVerifyCode(telephone, verifyCode);
//                    opResult.put("smsResult", isSend);
//                } catch (Exception e) {
//                    opResult.put("smsResult", false);
//                    throw e;
//                } finally {
//                    latch.countDown();
//                }
//            }).start();

            // 解耦为消息队列的方式下发验证码
            new Thread(() -> {
                Map<String, String> phoneAndCode = new HashMap<>();
                phoneAndCode.put(TelephoneMsgService.sendToKey, telephone);
                phoneAndCode.put(TelephoneMsgService.VERIFY_CODE_KEY, verifyCode);
                Message<Map<String, String>> message = MessageBuilder.withPayload(phoneAndCode).build();
                rabbitTemplate.convertAndSend(verifyCodeExchangeName, phoneVerifyCodeQueueRoutingKey, message);
            }).start();



            if (add > 0) {

                new Thread(() -> {
                    CommonUser user = commonUserMapper.getUserByTelephone(telephone);
                    if (user == null) {
                        CommonUser newUser = new CommonUser();
                        String userID = generateUserID(telephone);
                        newUser.setTelephone(telephone);
                        newUser.setUserID(userID);

                        Status status = new Status();
                        status.setStatusCode(statusMapper.getNormalStatusCode());
                        newUser.setStatus(status);

                        commonUserMapper.addCommonUser(newUser);
                    }
                }).start();

                return RespUtil.baseSuccess();
            } else {
                return RespUtil.baseFail();
            }
        }
        return RespUtil.illegalParameter();
    }

    @Override
    @Transactional
    public String verifyCodeWithTelephone(String telephone, String code) {
        BoundSetOperations<String, String> ops = telephoneCodeRedisTemplate.boundSetOps(telephone);
        Boolean isMember = ops.isMember(code);
        if (isMember) {
            CommonUser commonUser = commonUserMapper.getUserByTelephone(telephone);
            String userID;
            if (commonUser == null) {
                CommonUser newUser = new CommonUser();
                userID = generateUserID(telephone);
                newUser.setTelephone(telephone);
                newUser.setUserID(userID);

                Status status = new Status();
                status.setStatusCode(statusMapper.getNormalStatusCode());
                newUser.setStatus(status);

                commonUserMapper.addCommonUser(newUser);
            } else {
                userID = commonUser.getUserID();
            }
            Map<String, String> payload = new HashMap<>();
            payload.put(PAYLOAD_USER_ID_KEY, userID);

            COMMON_USER_LOGIN_EVENT_POOL.submit(() -> {
                publishUserLoginEvent(userID);
            });



            return generateToken(payload);
        }

        errReason = WRONG_ACCOUNT_OR_PASSWORD;
        return null;
    }

    @Override
    @Transactional
    public String verifyCodeWithEmail(String email, String code) {
        // 邮件验证码校验，返回校验凭证信息token
        BoundSetOperations<String, String> ops = emailCodeRedisTemplate.boundSetOps(email);
        Boolean isMember = ops.isMember(code);
        if (isMember) { // 验证码校验通过，生成token下发
            String userID;
            CommonUser savedUser = commonUserMapper.getUserByEmail(email);
            if (savedUser == null) {
                // 第一次保存失败，进行第二次保存尝试
                CommonUser commonUser = new CommonUser();
                userID = generateUserID(email);
                commonUser.setUserID(userID);
                commonUser.setEmail(email);

                Status status = new Status();
                status.setStatusCode(statusMapper.getNormalStatusCode());
                commonUser.setStatus(status);

                commonUserMapper.addCommonUser(commonUser);
            } else {
                // 数据库存在信息，获取userID
                userID = savedUser.getUserID();
            }
            // 生成token下发
            Map<String, String> payload = new HashMap<>();
            payload.put(PAYLOAD_USER_ID_KEY, userID);
            String token = generateToken(payload);

            // 提交线程池，发布用户登录事件
            COMMON_USER_LOGIN_EVENT_POOL.submit(() -> {
                publishUserLoginEvent(userID);
            });

            return token;
        }
        errReason = WRONG_ACCOUNT_OR_PASSWORD;
        return null;
    }

    @Override
    boolean isExists(String userID) {
        return commonUserMapper.getUserByID(userID) != null;
    }

    boolean checkUserStatus(BaseUser user) {
        Integer normalCode = statusMapper.getNormalStatusCode();
        return user.getStatus().getStatusCode().equals(normalCode);
    }

    public String generateVerifyCode() {
        StringBuilder str = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < 6; i++) {
            str.append(random.nextInt(10));
        }
        return str.toString();
    }

    public Integer getErrReason() {
        return errReason;
    }

    @Override
    public CommonUser getCommonUserInfo(String token) {
        String userID = getUserIDFromToken(token);
        CommonUser commonUser = commonUserMapper.getUserByID(userID);
        return commonUser;
    }

    private void publishUserLoginEvent(String userID) {
        Message<String> message = MessageBuilder.withPayload(userID).build();
        rabbitTemplate.convertAndSend(userLoginEventExchange, userLoginEventQueueRoutingKey, message);
    }
}
